/*********************************************************************************
Organization 					: 				Georgia Institute of Technology
  
Institute 						:				Cognitive Computing Group(CCL)
 
Authors							: 				Santiago Ontanon
 												
Class							:				EpisodicCBR
 
Function						: 				The class contains the main Episodic Case Based
												Reasoning Module, that ivides the case base in
												two structures: the "Behavior Base" and the 
												"Episode Base". A behavior is <goal,procedure> and
												an episode is <behavior,gamestate,outcome>
*********************************************************************************/
package cbr.episodicCBR;


import java.io.File;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.PrintStream;
import java.util.HashMap;
import java.util.LinkedList;

import org.jdom.JDOMException;

import planninglanguage.goals.Goal;
import planninglanguage.plans.Plan;
import base.ProxyBot;
import base.WargusGameState;
import cbr.CBR;
import cbr.CBRAdaptation;
import cbr.CBRAdaptationCoordinates;
import cbr.CBRAdaptationUnits;

public class EpisodicCBR extends CBR {
	   
    public static PrintStream m_logger = null;
	EpisodicCaseBase m_CaseBase;
	
	/*
	 * This two tables are used to remember which plans belong to which behaviors during
	 * the retention stage. And also to remember which was the game state when a behavior
	 * was retrieved.
	 */
	HashMap<Plan,CaseBaseBehavior> m_planToBehaviorTable = null;
	HashMap<Plan,WargusGameState> m_planToGameStateTable = null;
	
	public EpisodicCBR() {
		
		if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: Constructor started...");

		try {
		      m_logger = new PrintStream( new FileOutputStream(new File("log/CBR_log.txt")) );
			  CBRAdaptation.m_logger = m_logger;
			  CBRAdaptationCoordinates.m_logger = m_logger;
			  CBRAdaptationUnits.m_logger = m_logger;
		} catch (FileNotFoundException e) {
			m_logger = null;
			e.printStackTrace();
		}
		
		m_CaseBase = new EpisodicCaseBase();
	
		try {
			/*
			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-adaptation_basics.xml",
					"traces/episodes-adaptation_basics.xml");			
			*/
			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-NWTR1_ballistas.xml",
					"traces/episodes-NWTR1_ballistas.xml");

			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-NWTR1_only_ballistas.xml",
					"traces/episodes-NWTR1_only_ballistas.xml");

			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-NWTR1_towers.xml",
					"traces/episodes-NWTR1_towers.xml");

			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-NWTR3_towers_ballistas.xml",
					"traces/episodes-NWTR3_towers_ballistas.xml");

			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-NWTR6_towers_footmen.xml",
					"traces/episodes-NWTR6_towers_footmen.xml");		

			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-GOW_knights.xml",
					"traces/episodes-GOW_knights.xml");

			m_CaseBase.loadCaseBaseFromXML(
					"traces/behaviors-GOW_rush.xml",
					"traces/episodes-GOW_rush.xml");

		} catch (JDOMException jde) {
			jde.printStackTrace();
		} catch (IOException ioe) {
			ioe.printStackTrace();
		} catch (Exception e) {
			e.printStackTrace();
		}		
		m_CaseBase.printSummary();
		
		if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: Constructor finished.");	
	}
	
	/*
	 * Runs the CBR module
	 */
	public void run() {
		
		if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR started");
	}

	protected void finalize() throws Throwable {
		CBRAdaptation.m_logger = null;
		m_logger.close();		
	}
	
	/*
	 * Tells the CBR that a new game is going to start. This method can be used
	 * to clear any tomperary data the CBR system might want to store during the game play
	 */
	public void reset() {
		m_planToBehaviorTable = new HashMap<Plan,CaseBaseBehavior>();
		m_planToGameStateTable = new HashMap<Plan,WargusGameState>();
	}
	
	/*
	 * Tells the CBR that to save the current case base to disk
	 */
	public void save() {
		try {		
			if (DEBUG_LEVEL>=1) m_CaseBase.printSummary();				
			m_CaseBase.saveCaseBaseToXML("traces/output-test-beh.xml","traces/output-test-ep.xml");
		} catch (Exception e) {
			e.printStackTrace();
		}		
	}	
	
	
	/*
	 * Retreive the best plan from the Plan library that is similar to a given goal 
	 * and a current gamestate. This plan and the contained list of basic actions 
	 * is used by the run time execution (the MRE class) to send commands to the Wargus/Stratagus 
	 * engine
	 */
	public Plan retrieveCases (Goal a_g, WargusGameState a_gameState) throws Exception
	{		        	
		CaseBaseBehavior best = null;
		float best_performance = -1;
		float best_goal_similarity = -1;
		float best_score = -1;
		
		if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: received a goal:\n " + a_g.toString());

		for(CaseBaseBehavior b:m_CaseBase.m_CB_behaviors) {
			float goal_similarity = 1.0f - a_g.distance(b.m_goal);
			float performance = predictPerformance(b,a_gameState);
			float score = goal_similarity + performance;

			if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: considering behavior " + b.m_ID + ", score: " + goal_similarity + " " + performance + " " + score);
			m_logger.println("EpisodicCBR: considering behavior " + b.m_ID + ", score: " + goal_similarity + " " + performance + " " + score);
			
			if (best_performance==-1 || best_goal_similarity==-1 || best_score==-1 || score>best_score) {
				best = b;
				best_performance = performance;
				best_goal_similarity = goal_similarity;
				best_score = score;
			}
		}
		
		if (best!=null) {
			Plan p = (Plan)best.m_plan.clone();
			m_planToBehaviorTable.put(p,best);
			m_planToGameStateTable.put(p,a_gameState);
			
			m_experimentsLogger.println("Retrieved Behavior for goal " + a_g.toString() + " (" + ProxyBot.getProxyBot().getCurrCycle() + "): " + best.m_ID + " (" + best_goal_similarity + "," + best_performance + ")");
			return p;
		} else {
			return null;
		}				
	}	
	public void resetBehaviors()
	{
		for(CaseBaseBehavior b:m_CaseBase.m_CB_behaviors) {
			b.reset();
		}
		
	}

	public CaseBaseBehavior createCaseBaseBehavior(WargusGameState gamestate, CaseBaseBehavior oldbehavior,Plan newplan,String id)
	{		
		return new CaseBaseBehavior(id,oldbehavior.getGoal(),newplan);
	}
	
	public void addCaseBaseBehavior(CaseBaseBehavior ncbb)
	{
		m_CaseBase.addBehavior(ncbb);
	}
	
	public CaseBaseEpisode createCaseBaseEpisode(WargusGameState gamestate, String id, float outcome)
	{		
		return new CaseBaseEpisode(id,gamestate,outcome);
	}
	
	public void addCaseBaseEpisode(CaseBaseEpisode ncbe)
	{
		m_CaseBase.addEpisode(ncbe);
	}	
	
	public CaseBaseBehavior retrieveCaseBehavior (Goal a_g, WargusGameState a_gameState) throws Exception
	{		        	
		CaseBaseBehavior best = null;
		float best_performance = -1;
		float best_goal_similarity = -1;
		float best_score = -1;
		
		if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: received a goal:\n " + a_g.toString());

		for(CaseBaseBehavior b:m_CaseBase.m_CB_behaviors) {
			float goal_similarity = 1.0f - a_g.distance(b.m_goal);
			float performance = predictPerformance(b,a_gameState);
			float score = goal_similarity + performance;

			if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: considering behavior " + b.m_ID + ", score: " + goal_similarity + " " + performance + " " + score);
			m_logger.println("EpisodicCBR: considering behavior " + b.m_ID + ", score: " + goal_similarity + " " + performance + " " + score);
			
			if (best_performance==-1 || best_goal_similarity==-1 || best_score==-1 || score>best_score) {
				best = b;
				best_performance = performance;
				best_goal_similarity = goal_similarity;
				best_score = score;
			}
		}
		return best;			
	}	
	
	
	
	float predictPerformance(CaseBaseBehavior a_b,WargusGameState gameState) throws Exception {
		LinkedList<Float> m_distances = new LinkedList<Float>();
		LinkedList<Float> m_performances = new LinkedList<Float>();
		
		for(CaseBaseEpisode e : m_CaseBase.m_CB_episodes) {
			if (e.m_behaviorID.equals(a_b.m_ID)) {
				m_distances.add(gameStateDistance(e.m_gameState, gameState));
				m_performances.add(e.m_outcome);
			}
		}
		
		// Sort the list (bubble sort):
		{
			int iteration,i;
			boolean changes = false;
			iteration = 0;
			do{			
				changes = false;
				for(i=iteration;i<m_distances.size()-1;i++) {
					if (m_distances.get(i)>m_distances.get(i+1)) {
						Float tmp;
						
						tmp = m_distances.get(i);
						m_distances.set(i,m_distances.get(i+1));
						m_distances.set(i+1,tmp);

						tmp = m_performances.get(i);
						m_performances.set(i,m_performances.get(i+1));
						m_performances.set(i+1,tmp);
					}
				}
				
				iteration++;
			}while(changes);
		}
		
		// Average the top 5:
		{
			int i;
			float total_weight = 1.0f;	// Initialized to 1 so that we bias the results pesimistically if the
										// retrieved episodes contain situations with low similarity
			float total_performance = 0;
			
			for(i=0;i<5 && i<m_distances.size();i++) {
				total_weight += (1.0 - m_distances.get(i));
				total_performance += m_performances.get(i)*(1.0 - m_distances.get(i));
				
				if (DEBUG_LEVEL>=2) System.out.println("EpisodicCBR: (distance,performance) = (" + m_distances.get(i) + "," + m_performances.get(i) + ")");
				m_logger.println("EpisodicCBR: (distance,performance) = (" + m_distances.get(i) + "," + m_performances.get(i) + ")");
			}
			
			if (total_weight == 1.0f) return 0.5f;
			return total_performance / total_weight;
		}		
	}
		
	float gameStateDistance(WargusGameState s1, WargusGameState s2) {
		return s1.distance(s2,0.25f, 0.25f, 0.25f, 0.25f);
	}
	
	/*
	 * Adapt the Plan retrieved from the plan library to the current game 
	 * state so that the plan is specific to the current game situation 
	 */
	public Plan adaptPlan(Plan a_p,WargusGameState a_gamestate,int playerID) throws Exception
	{
		return CBRAdaptation.adaptPlan(a_p,a_gamestate,playerID);
	}	
	
	/*
	 * Retain a case base object based on successful execution of the 
	 * case base after successul adaptation
	 */

	public void retain(Plan a_p, float success, long a_time)
	{
		CaseBaseBehavior b;
		WargusGameState gs;
		if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: case retention...");
		m_logger.println("EpisodicCBR: case retention...");
		
		b = m_planToBehaviorTable.get(a_p);
		gs = m_planToGameStateTable.get(a_p);

		if (b==null || gs==null) {
			if (DEBUG_LEVEL>=1) System.out.println("EpisodicCBR: case retention failed since, plan is not recognized...");
			m_logger.println("EpisodicCBR: case retention failed since, plan is not recognized...");
		} else {
			if (DEBUG_LEVEL>=1) System.out.println("EpisocidCBR: generating a new episode for the behavior: " + b.m_ID);
			m_logger.println("EpisocidCBR: generating a new episode for the behavior: " + b.m_ID);
			
			CaseBaseEpisode e = new CaseBaseEpisode(b.m_ID,gs,success);
			m_CaseBase.addEpisode(e);
			
			m_planToBehaviorTable.remove(a_p);
			m_planToGameStateTable.remove(a_p);			

			m_experimentsLogger.println("New episode retained (" + ProxyBot.getProxyBot().getCurrCycle() + ") :" + b.m_ID + " -> " + success);
		}
		
	}	

}